/* ===================================================================
 * File:
 *		st7735.c
 *
 * Brief Description:
 *		Function definitions for ST7735 driver.
 *
 * Device:
 *		ST7735 LCD Driver
 *		Freescale FRDM-KL25Z platform
 *
 * Software:
 *		Keil uVision4
 *
 * Author:
 *		Krzysztof Blazewicz (blazewicz.krzysztof@gmail.com)
 *
 * Date:
 *		19.05.2013
 * -------------------------------------------------------------------
 * Software uses hardware SPI interface with hardware controled 
 * Slave Select output. It does not use DMA controller.
 * ===================================================================*/

#include <stdarg.h>
#include "st7735.h"
#include "st7735_font.h"

/* double stack used in fillPolygon function */
#define pSSIZE 512 /* stack size */
static uint8_t xStack[pSSIZE];
static uint8_t yStack[pSSIZE];
static uint16_t pSP=0; /* stack pointer */

/* some useful macros */
#define swap(a,b) {int16_t t=a; a=b; b=t;}
#define abs(a) (((a)>0)?(a):-(a))

/* delay function */
extern void delay_ms(uint32_t);

/* -------------------------------------------------------------------------------
 * GPIO configuration
 * ------------------------------------------------------------------------------- */
__INLINE void GPIO_Config(void)
{
	SIM->SCGC5 |= SIM_SCGC5_PORTE_MASK;
	
	PORTE->PCR[2] |= PORT_PCR_MUX(2); /* SCK */
	PORTE->PCR[3] |= PORT_PCR_MUX(5); /* MOSI (MOMI) */
	PORTE->PCR[4] |= PORT_PCR_MUX(2); /* /CS */
	PORTE->PCR[5] |= PORT_PCR_MUX(1); /* A0 */
	PORTE->PCR[20] |= PORT_PCR_MUX(1); /* RESET */
	
	/* only A0 and RESET pins need to be set as outputs */
	FPTE->PDDR |= (1UL<<5)|(1UL<<20);
}

/* -------------------------------------------------------------------------------
 * SPI configuration
 * ------------------------------------------------------------------------------- */
__INLINE void SPI_Config(void)
{	
	uint8_t C1 = 0;
	uint8_t C2 = 0;
	
	SIM->SCGC4 |= SIM_SCGC4_SPI1_MASK;
	
	C1 |= SPI_C1_CPOL_MASK; /* CPOL=1 */
	C1 |= SPI_C1_CPHA_MASK; /* CPHA=1 */
	C1 |= SPI_C1_MSTR_MASK; /* Master Mode */
	C1 |= SPI_C1_SPE_MASK; /* SPI Enable */
	C1 |= SPI_C1_SSOE_MASK; /* Slave Select Output Enable */
	
	C2 |= SPI_C2_SPC0_MASK; /* Bi-directional mode */
	C2 |= SPI_C2_BIDIROE_MASK; /* MIMO as output */
	C2 |= SPI_C2_MODFEN_MASK; /* Automatic Slave Select */
	
	/* BR = bus clock = 24MHz - nothing need to be changed */
	
	SPI1->C1 = C1; /* store C1 */
	SPI1->C2 = C2; /* store C2 */
	SPI1->BR = 0x00; /* store BR */
}

/* -------------------------------------------------------------------------------
 * Exchange data byte over SPI
 * ------------------------------------------------------------------------------- */
uint8_t SPI_Transfer(uint8_t data)
{
	while ((SPI1->S & SPI_S_SPTEF_MASK) == 0)
		/* wait for data buffer empty */;
	SPI1->D = data;
	while((SPI1->S & SPI_S_SPRF_MASK) == 0)
		/* wait for data in buffer */;
	return SPI1->D;
}

// uint16_t ST7735_ReadPixel(void){
// 	uint16_t value=0;
// 	
// 	A0_LOW();	
// 	SPI_Transfer(ST7735_RAMRD);
// 	
// 	SPI1->C1 &= ~SPI_C1_CPOL_MASK; // CPOL=0
// 	SPI1->C2 &= ~SPI_C2_BIDIROE_MASK; // MIMO as input
// 	
// 	A0_HIGH();	
// 	SPI_Transfer(0x00); // dummy read
// 	value = SPI_Transfer(0x00) << 8; // high byte
// 	value |= SPI_Transfer(0x00); // low byte
// 	
// 	SPI1->C1 |= SPI_C1_CPOL_MASK; // CPOL=1
// 	SPI1->C2 |= SPI_C2_BIDIROE_MASK; // MIMO as output
// 	
// 	return value;
// }

/* -------------------------------------------------------------------------------
 * Send 16-bit color information for currentnly addressed pixel
 * ------------------------------------------------------------------------------- */
void ST7735_WritePixel(uint16_t color)
{
	A0_HIGH();
	SPI_Transfer(color>>8);
	SPI_Transfer(color);
}

/* -------------------------------------------------------------------------------
 * Send Command with optional Parameters
 * usage:
 * 	ST7735_WriteCommand( command, N [, param1, param2, ..., paramN]);
 *  if N=0 no parameters will be sent
 *  text in [...] is optional
 * ------------------------------------------------------------------------------- */
void ST7735_WriteCommand(uint8_t command, uint8_t paramNum, ...)
{
	va_list arg;
	
	A0_LOW();
	SPI_Transfer(command);
	
	if (paramNum){
		A0_HIGH();
		va_start(arg, paramNum);
		while(paramNum--)
			SPI_Transfer(va_arg(arg, int));
		va_end(arg);
	}
}

/* -------------------------------------------------------------------------------
 * Initialize driver
 * ------------------------------------------------------------------------------- */
void ST7735_Init(void)
{
	/* basic configuration */
	GPIO_Config();
	SPI_Config();
	
	/* hardware reset */
	RST_HIGH();
	delay_ms(100);
	RST_LOW();
	delay_ms(100);
	RST_HIGH();
	delay_ms(150);
	
	/* software reset */
	ST7735_WriteCommand(ST7735_SWRESET, 0);
	delay_ms(150);
	
	/* out of sleep mode */
	ST7735_WriteCommand(ST7735_SLPOUT, 0);
	delay_ms(150);

	/* Frame Rate Configuration
	 * frame rate = 333kHz/((RTNA+20) * (LINE + FPA + BPA))
	 * same rate setting for all modes, normal, idle, partial */
	ST7735_WriteCommand(ST7735_FRMCTR1, 3, 0x01/*RTNA*/, 0x2C/*FPA*/, 0x2D/*BPA*/); // normal mode
	ST7735_WriteCommand(ST7735_FRMCTR2, 3, 0x01, 0x2C, 0x2D); //idle mode
	ST7735_WriteCommand(ST7735_FRMCTR3, 6, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D);// partial mode

	/* Display Inversion Control */
	ST7735_WriteCommand(ST7735_INVCTR, 1, 0x07);

	/* Power Configuration */
	ST7735_WriteCommand(ST7735_PWCTR1, 3, 0xA2, 0x02, 0x84);// -4.6V, AUTO mode
	ST7735_WriteCommand(ST7735_PWCTR2, 1, 0xC5);// VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
	ST7735_WriteCommand(ST7735_PWCTR3, 2, 0x0A, 0x00);// Opamp current small, Boost frequency
	ST7735_WriteCommand(ST7735_PWCTR4, 2, 0x8A, 0x2A);// BCLK/2, Opamp current small & Medium low
	ST7735_WriteCommand(ST7735_PWCTR5, 2, 0x8A, 0xEE);
	ST7735_WriteCommand(ST7735_VMCTR1, 1, 0x0E);

	/* no color inversion */
	ST7735_WriteCommand(ST7735_INVOFF, 0);
	
	/* 16-bit color depth mode */
	ST7735_WriteCommand(ST7735_COLMOD, 1, 0x05);
	
	/* mirror in both axes */
	ST7735_WriteCommand(ST7735_MADCTL, 1, 0xC0);

	/* configure gamma curves */
	ST7735_WriteCommand(ST7735_GAMCTRP1, 16, \
		0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, \
		0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10);
	ST7735_WriteCommand(ST7735_GAMCTRN1, 16, \
		0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, \
		0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10);

	/* turn display on */
	ST7735_WriteCommand(ST7735_DISPON, 0);
	delay_ms(100);

	/* set display in normal mode (non-partial) */
	ST7735_WriteCommand(ST7735_NORON, 0);
	delay_ms(10);
	
 	/* display splash screen */
	drawFreescaleLogo();
	drawString(60,120,"FREESCALE",COLOR_BLACK);
	drawString(55,130,"FRDM-KL25Z",COLOR_BLACK);
}

/* -------------------------------------------------------------------------------
 * Configure Address Window in drivers RAM where data could be changed
 * ------------------------------------------------------------------------------- */
void ST7735_SetAddrWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
	ST7735_WriteCommand(ST7735_CASET, 4, 0x00, x0, 0x00, x1-1);
	ST7735_WriteCommand(ST7735_RASET, 4, 0x00, y0, 0x00, y1-1);
}

/* -------------------------------------------------------------------------------
 * Fill Rectangle
 * ------------------------------------------------------------------------------- */
void fillRectangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t color)
{
	register int i;
	
	if (x0 > x1)
		swap(x0,x1);
	if (y0 > y1)
		swap(y0,y1);
	
	i = (x1-x0)*(y1-y0);
	
	ST7735_SetAddrWindow(x0,y0,x1,y1);
	ST7735_WriteCommand(ST7735_RAMWR, 0);
	A0_HIGH();
	while (i--){
		SPI_Transfer(color>>8);
		SPI_Transfer(color);
	}
}

/* -------------------------------------------------------------------------------
 * Draw Single Pixel
 * ------------------------------------------------------------------------------- */
void drawPixel(uint8_t x, uint8_t y, uint16_t color)
{
 	if( x > 127 || y > 159 )
 		return;
	ST7735_SetAddrWindow(x, y, x+1, y+1);
	ST7735_WriteCommand(ST7735_RAMWR, 0);
	ST7735_WritePixel(color);
}

/* -------------------------------------------------------------------------------
 * Draw Line with Besenham's Line Algorithm (from en.wikipedia.org)
 * ------------------------------------------------------------------------------- */
void _besenhamsLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color \
		,void (*_putPixel)(uint8_t, uint8_t, uint16_t))
{
	int16_t dx, dy, steep, err, ystep;
	
	steep = abs(y1 - y0) > abs(x1 - x0);
	
	if (steep){
		swap(x0, y0);
		swap(x1, y1);
	}
	if (x0 > x1){
		swap(x0, x1);
		swap(y0, y1);
	}
	dx = x1 - x0;
	dy = abs(y1 - y0);
	err = dx / 2;

	if (y0 < y1)
		ystep = 1;
	else
		ystep = -1;

	for (; x0 <= x1; x0++) {
		if (steep)
			(*_putPixel)(y0, x0, color);
		else
			(*_putPixel)(x0, y0, color);
		err -= dy;
		if (err < 0){
			y0 += ystep;
			err += dx;
		}
	}
}

void pushPixel(uint8_t x, uint8_t y, uint16_t color){
	if (pSP < pSSIZE){
		xStack[pSP] = x;
		yStack[pSP] = y;
		pSP++;
	}
}

void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)
{
	_besenhamsLine(x0,y0,x1,y1,color,&drawPixel);
}

void calcLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1)
{
	_besenhamsLine(x0,y0,x1,y1,0,&pushPixel);
}
/* -------------------------------------------------------------------------------
 * Draw Circle or Disk with Besenham's Circle Algorithm (from en.wikipedia.org)
 * ------------------------------------------------------------------------------- */
void _besenhamsCircle(int16_t cx, int16_t cy, int16_t radius, uint16_t color, \
		void (*_circleDrawer)(int16_t, int16_t, int16_t, int16_t, uint16_t) )
{
  int error = -radius;
  int x = radius;
  int y = 0;
 
  while (x >= y)
  {
	(*_circleDrawer)(cx, cy, x, y, color);
	if (x != y) (*_circleDrawer)(cx, cy, y, x, color);
 
    error += y;
    ++y;
    error += y;
	  
    if (error >= 0)
    {
      error -= x;
      --x;
      error -= x;
    }
  }
}

void plot4points(int16_t cx, int16_t cy, int16_t x, int16_t y, uint16_t color)
{
  drawPixel(cx + x, cy + y, color);
  if (x != 0) drawPixel(cx - x, cy + y, color);
  if (y != 0) drawPixel(cx + x, cy - y, color);
  drawPixel(cx - x, cy - y, color);
}

void plot4lines(int16_t cx, int16_t cy, int16_t x, int16_t y, uint16_t color)
{
	fillRectangle(cx,cy+y,cx+x,cy+y+1,color);
	if (x != 0) fillRectangle(cx,cy+y,cx-x,cy+y+1,color);
	if (y != 0) fillRectangle(cx,cy-y,cx+x,cy-y+1,color);
	fillRectangle(cx,cy-y,cx-x,cy-y+1,color);
}
void drawCircle(int16_t cx, int16_t cy, int16_t radius, uint16_t color)
{
	_besenhamsCircle(cx,cy,radius,color,&plot4points);
}

void fillCircle(int16_t cx, int16_t cy, int16_t radius, uint16_t color)
{
	_besenhamsCircle(cx,cy,radius,color,&plot4lines);
}

/* -------------------------------------------------------------------------------
 * Draw Polygon
 * ------------------------------------------------------------------------------- */
void drawPolygon(uint8_t n, uint8_t *x, uint8_t *y, uint16_t color)
{
	register int i;
	for(i=0;i<n-1;i++)
		drawLine(x[i],y[i],x[i+1],y[i+1],color);
	drawLine(x[i],y[i],x[0],y[0],color);
}

/* -------------------------------------------------------------------------------
 * Fill Polygon
 *		Lame scanline algorithm, works properly only if horizontal line can't
 *		cross figure more than 2 times. Works fine for triangles and quadrilaterals.
 *		Need further improvements.
 * ------------------------------------------------------------------------------- */
void fillPolygon(uint8_t n, uint8_t *x, uint8_t *y, uint16_t color)
{
	register int i,j;
	uint8_t used[pSSIZE];
	for(i=0;i<n-1;i++)
		calcLine(x[i],y[i],x[i+1],y[i+1]);
	calcLine(x[i],y[i],x[0],y[0]);
	
	for(i=0; i<pSP; i++)
		used[i]=0;
	
	for(i=0; i<pSP; i++)
		if(!used[i])
			for(j=i+1;j<pSP;j++)
				if(!used[j] && yStack[i]==yStack[j]){
					fillRectangle(xStack[i],yStack[i], xStack[j],yStack[i]+1, color);
					used[j] = used[i] = 1;
				}
	
	pSP=0;
}

/* -------------------------------------------------------------------------------
 * Draw Ortogonal Rectangle
 * ------------------------------------------------------------------------------- */
void drawRectangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t color)
{
	uint8_t x[4], y[4];
	x[0]=x[3]=x0;
	x[1]=x[2]=x1;
	y[0]=y[1]=y0;	
	y[2]=y[3]=y1;	
	
	drawPolygon(4, x, y, color);
}

/* -------------------------------------------------------------------------------
 * Draw Triangle
 * ------------------------------------------------------------------------------- */
void _drawTriangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color\
		,void (*_plotTriangle)(uint8_t, uint8_t*, uint8_t*, uint16_t))
{
	uint8_t x[3], y[3];
	x[0]=x0; x[1]=x1; x[2]=x2;
	y[0]=y0; y[1]=y1; y[2]=y2;
	
	(*_plotTriangle)(3, x, y, color);
}

void drawTriangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color)
{
	_drawTriangle(x0,y0,x1,y1,x2,y2,color,&drawPolygon);
}

void fillTriangle(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color)
{
	_drawTriangle(x0,y0,x1,y1,x2,y2,color,&fillPolygon);
}

/* -------------------------------------------------------------------------------
 * Draw Freescale Logo
 * ------------------------------------------------------------------------------- */
void drawFreescaleLogo(void){
	register int i,j;
	uint8_t x[8][4], y[8][4];
	int16_t xoffset = 40;
	int16_t yoffset = 30;
	int16_t x0[] = {0,23,37,14};
	int16_t y0[] = {0,-14,-7,7};
	int16_t xo[] = {0,21,42,11,-22,32,0,-32};
	int16_t yo[] = {0,11,22,42,61,53,72,92};
	int16_t color[] = {0xE305,0xF5A4,0xE305,0xF5A4,0xE305,0xE305,0xF5A4,0xE305};

	for (i=0; i<8; i++)
		for (j=0; j<4; j++){
			x[i][j] = (uint8_t) (x0[j]+xo[i]+xoffset);
			y[i][j] = (uint8_t) (y0[j]+yo[i]+yoffset);
		}

	fillRectangle(0,0,128,160,COLOR_WHITE);
	for (i=0; i<8; i++)
		fillPolygon(4,x[i],y[i],color[i]);
}

/* -------------------------------------------------------------------------------
 * Draw Single Character
 * ------------------------------------------------------------------------------- */
void drawChar(uint8_t x, uint8_t y,char c, uint16_t color)
{
	register int sh,l;
	
	ST7735_SetAddrWindow(x,y,x+5,y+8);
	for(l=0;l<8;l++)
		for(sh=0;sh<5;sh++)
			if((font8x5[c][l]<<sh)&0x10)
				drawPixel(x+sh,y+l,color);
}

/* -------------------------------------------------------------------------------
 * Draw String
 * ------------------------------------------------------------------------------- */
void drawString(uint8_t x, uint8_t y, char *str, uint16_t color)
{
	while(*str){
		drawChar(x,y,*(str++),color);
		x += 6;
		if (x > 116 && *str){
			drawChar(x,y,0x0A,color);
			x = 2;
			y += 10;
		}
	}
}
